﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using Ensynch.Collections.Generic;
using Microsoft.ResourceManagement.Client;
using Microsoft.ResourceManagement.ObjectModel;

namespace Ensynch.FIM
{
    public class FimQueryResult : IDisposable
    {
        // Create a logger for use in this class.
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(
                System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private FimQuery query;
        private Control context;

        #region Public properties

        private DataView view;
        public DataView DataView
        {
            get
            {
                if (view == null)
                {
                    CreateTable();
                }
                return view;
            }
        }

        private DataTable table;
        public DataTable DataTable
        {
            get
            {
                if (table == null)
                {
                    CreateTable();
                }
                return table;
            }
        }

        private DataRow currentRow;
        public DataRow CurrentRow
        {
            get
            {
                return currentRow;
            }
        }

        #endregion

        #region Private properties

        private EnumerationResultEnumerator enumerator;
        private EnumerationResultEnumerator Enumerator
        {
            get
            {
                if (enumerator == null)
                {
                    string xpath = query.NormalizedXPath;
                    log.Debug("[get_Enumerator] Calling DefaultClient.Enumerate:\r\n" + xpath);
                    enumerator = (EnumerationResultEnumerator)query.Tools.FimWsProxy.DefaultClient.Enumerate(
                        xpath, query.EnumerateAttributes, query.MaxRows, query.SortAttributes);
                }
                return enumerator;
            }
        }

        private SerializableDictionary<string, RmFactory.RmAttributeType> attributeTypeDictionary;
        public SerializableDictionary<string, RmFactory.RmAttributeType> AttributeTypeDictionary
        {
            get
            {
                if (attributeTypeDictionary == null)
                {
                    attributeTypeDictionary = query.Tools.GetAttributeTypeDictionary(query.ObjectType);
                }
                return attributeTypeDictionary;
            }
        }

        #endregion

        public FimQueryResult(FimQuery query, Control context)
        {
            this.query = query;
            this.context = context;
        }

        #region Public methods

        /// <summary>
        /// Exposing this publicly so that this can be done asynchronously from
        /// the FIM Query Tool.
        /// </summary>
        public void CreateTable()
        {
            StringBuilder buffer = new StringBuilder();
            buffer.AppendLine("[createTable]");
            table = new DataTable();
            foreach (DataColumn col in query.DataColumns)
            {
                // Do not add col directly, or you will get this error:
                // "Column 'BLAH' already belongs to another DataTable."
                table.Columns.Add(col.ColumnName, col.DataType);
                buffer.AppendFormat("{0}: {1}\r\n", col.ColumnName.PadRight(16), col.DataType);
            }
            if (query.WhereConditions != null)
            {
                view = new DataView(
                    table, query.WhereConditions, "", DataViewRowState.CurrentRows);
            }
            else
            {
                view = new DataView(table);
            }
            log.Debug(buffer.ToString());
        }

        public void ReadAll()
        {
            DateTime start = DateTime.Now;
            DateTime finish;
            StringBuilder buffer;
            TimeSpan elapsed;

            foreach (RmResource obj in Enumerator)
            {
                DataRow row = resolveAttributeValues(obj);

                if (context != null)
                {
                    context.Invoke(new MethodInvoker(
                        delegate
                        {
                            DataTable.Rows.Add(row);
                        }));
                }
                else
                {
                    DataTable.Rows.Add(row);
                }
            }

            finish = DateTime.Now;
            buffer = new StringBuilder();
            buffer.AppendLine("[ReadAll] Successfully completed.");
            elapsed = finish - start;
            buffer.AppendLine("Total elapsed time: " + elapsed.ToString());
            log.Debug(buffer.ToString());
        }

        private DataRow resolveAttributeValues(RmResource obj)
        {
            DataRow row = DataTable.NewRow();

            #region Wait for all threads
            List<WaitHandle> waitHandles = new List<WaitHandle>();
            DateTime startIter = DateTime.Now;
            #endregion

            foreach (FimQueryAttribute attribute in query.FimQueryAttributes)
            {
                IAsyncResult result = attribute.ResolveAttributeValue(obj, ref row);
                #region Wait for all threads
                if (result != null)
                {
                    waitHandles.Add(result.AsyncWaitHandle);
                }
                #endregion
            }

            #region Wait for all threads
            if (waitHandles.Count > 0)
            {
                waitAll(waitHandles.ToArray());
                DateTime endIter = DateTime.Now;
                TimeSpan elapsed = endIter - startIter;
                log.Debug("[resolveAttributeValues] iteration elapsed time: " + elapsed.ToString());
            }
            #endregion

            return row;
        }

        /// <summary>
        /// Read one record from the enumerator and store it in the CurrentRow.
        /// </summary>
        /// <returns></returns>
        public bool Read()
        {
            if (!Enumerator.MoveNext())
            {
                return false;
            }

            RmResource obj = Enumerator.Current;
            currentRow = resolveAttributeValues(obj);

            #region No need to add it to the table?
            //if (context != null)
            //{
            //    context.Invoke(new MethodInvoker(
            //        delegate
            //        {
            //            DataTable.Rows.Add(currentRow);
            //        }));
            //}
            //else
            //{
            //    DataTable.Rows.Add(currentRow);
            //}
            #endregion

            return true;
        }

        #endregion

        #region Private methods

        /// <summary>
        /// Use this instead of WaitHandle.WaitAll() to avoid this limitation:
        /// "The number of WaitHandles must be less than or equal to 64."
        /// </summary>
        private void waitAll(WaitHandle[] handles)
        {
            if (handles == null)
            {
                return;
            }
            foreach (WaitHandle handle in handles)
            {
                handle.WaitOne();
            }
        }

        //private object resolveAttributeValue(RmResource obj, string attributeName)
        //{
        //    if (!obj.ContainsKey(new RmAttributeName(attributeName)) || obj[attributeName] == null)
        //    {
        //        return System.DBNull.Value;
        //    }

        //    RmAttributeValue rmAttribute = obj[attributeName];
        //    RmFactory.RmAttributeType rmAttributeType;
        //    if (AttributeTypeDictionary.ContainsKey(attributeName))
        //    {
        //        rmAttributeType = AttributeTypeDictionary[attributeName];
        //    }
        //    else
        //    {
        //        rmAttributeType = RmFactory.RmAttributeType.String;
        //    }

        //    if (rmAttribute.IsMultiValue)
        //    {
        //        StringBuilder buffer = new StringBuilder();
        //        bool first = true;
        //        foreach (var value in rmAttribute.Values)
        //        {
        //            if (!first)
        //            {
        //                buffer.Append("; ");
        //            }

        //            if (rmAttributeType == RmFactory.RmAttributeType.Reference)
        //            {
        //                buffer.Append(value.ToString());
        //                //buffer.Append(tools.PrintReference(
        //                //    value.ToString(),
        //                //    format));
        //            }
        //            else
        //            {
        //                buffer.AppendLine(value.ToString());
        //            }

        //            first = false;
        //        }
        //        return buffer.ToString();
        //    }
        //    else if (rmAttribute.Value == null)
        //    {
        //        return System.DBNull.Value;
        //    }
        //    //else if (attribute.Equals(FimTools.ATTRIBUTE_OBJECT_ID))
        //    //{
        //    //    return tools.PrintReference(
        //    //        obj[attribute].Value.ToString(),
        //    //        FimTools.FORMAT_GUID);
        //    //}
        //    //else if (attrTypeDict[attribute].Equals(FimTools.TYPE_REFERENCE))
        //    //{
        //    //    return tools.PrintReference(
        //    //        obj[attribute].Value.ToString(),
        //    //        format);
        //    //}
        //    else
        //    {
        //        switch (rmAttributeType)
        //        {
        //            case RmFactory.RmAttributeType.Binary:
        //                return ((RmBinary)rmAttribute.Value).ValueAsBinary;
        //            default:
        //                return rmAttribute.Value;
        //        }
        //    }
        //}

        #endregion


        #region IDisposable Members

        public void Dispose()
        {
            if (table != null)
            {
                table.Dispose();
            }
        }

        #endregion
    }
}
